package stochastic;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import classictravellersalesman.ClassicTravellerPath;
import classictravellersalesman.ThreadSolution;

import jcuda.driver.CUcontext;
import jcuda.driver.CUdevice;
import jcuda.driver.CUfunction;
import jcuda.driver.CUmodule;
import jcuda.driver.JCudaDriver;
import jcuda.runtime.*;

public class SimulatedAnealing {
	private Solution currentSolution;
	private int numberOfAcceptance;
	private int N;
	
	private CoolingShedule shedule;
	
	public SimulatedAnealing(Solution S)
	{
		shedule = new defaultCoolingShedule(S);
		numberOfAcceptance = 0;
		currentSolution = S;
		N = 400*currentSolution.getDegreeOfFreedom();
	}
	
	public void next() {
		boolean isAccept = false;
		
		for(int i=0;i<N;++i) {
			Solution candidat = currentSolution.perturb();
			if(candidat.getCost() <= currentSolution.getCost() ) {
				if(candidat.getCost() < currentSolution.getCost()) {
					isAccept = true;
				}
				currentSolution = candidat;
			}
			else if(candidat.getCost() > currentSolution.getCost()) {
				double delta = candidat.getCost() - currentSolution.getCost();
				if(Math.random() < Math.exp(-delta/shedule.getTemperature())) {
					currentSolution = candidat;
                    isAccept = true; 
				}
			}
		}
		
		if(isAccept == true) {
			numberOfAcceptance = 0;
		}
		else {
			++numberOfAcceptance;
		}
		shedule.anneal();
			
	}
	
	public void start() {
		final int end=20;
		while(!isEnd()) {
			long now = System.currentTimeMillis();
			next();
			System.out.println(System.currentTimeMillis()-now);
			System.out.println(String.format("T is %1$f. Cost is %2$f. End is %3$d", 
								shedule.getTemperature(), currentSolution.getCost(), numberOfAcceptance));
		}
		
		System.out.println(currentSolution.toString());
		System.out.println(currentSolution.getCost());
	}
	
	public void CUDAStart() throws IOException {
		
		JCudaDriver.cuInit(0);
		JCudaDriver.setExceptionsEnabled(true);
		CUcontext context = new CUcontext();
	    CUdevice device = new CUdevice();
	    JCudaDriver.cuDeviceGet(device, 0);
	    JCudaDriver.cuCtxCreate(context, 0, device);
	    
	    cudaDeviceProp prop = new cudaDeviceProp();
	    JCuda.cudaGetDeviceProperties(prop, 0);
	    
	    int maxThreads = prop.maxThreadsPerBlock;
	    int numThreads = 256;//Math.min(512, maxThreads);
	    int perturbPerThread = (N-1)/numThreads+1;
	    
	    currentSolution.loadCUDA(perturbPerThread,numThreads, shedule.getTemperature());
	    int NNN = 0;
	    		
		while(!isEnd()) {
			long now = System.currentTimeMillis();
			int st = 1;
			if(shedule.getTemperature()<3)
				st=6;
			for(int i=0;i<st;++i) {
				currentSolution.launchKernel();
				//System.out.println("Computation:"+(System.currentTimeMillis()-now));
				int ok=0;
				if(i==st-1)
					ok=1;
				Solution tempSolution = currentSolution.cudaChoiceSolution(numThreads, ok);
				double was = tempSolution.getCost();
				double is = currentSolution.getCost();
				//System.out.println("COST:"+was);
				if(Math.abs(tempSolution.getCost()-currentSolution.getCost())<1e-10)
					++numberOfAcceptance;
				else
					numberOfAcceptance = 0;
				currentSolution = tempSolution;
			}
			
			
			System.out.println(String.format("T is %1$f. Cost is %2$f. End is %3$d", 
		    					shedule.getTemperature(), currentSolution.getCost(), numberOfAcceptance));
			//System.out.println(currentSolution.toString());
			shedule.anneal();
			++NNN;
		}
		System.out.println(currentSolution.toString());
		System.out.println(currentSolution.getCost());
		System.out.println(NNN);
		currentSolution.cudaClean();
		
	}
	
public void CPUParallelStart() throws IOException {
		
	    int numThreads = 2;
	    int perturbPerThread = (N-1)/numThreads+1;
	    
	    int NNN = 0;
	    		
		while(!isEnd()) {
			long now = System.currentTimeMillis();
			int st = 1;
			if(shedule.getTemperature()<3)
				st=6;
			for(int i=0;i<st;++i) {
				ThreadSolution threads[] = new ThreadSolution[numThreads];
				HashSet<Integer> successThreads = new HashSet<Integer>();
				HashSet<Integer> endThreads = new HashSet<Integer>();
				for(int j=0;j<threads.length;++j) {
					threads[j] = new ThreadSolution(
							((ClassicTravellerPath)currentSolution).clone(),
							perturbPerThread,
							successThreads,
							endThreads,
							shedule.getTemperature(),
							j);
					Thread a = new Thread(threads[j]);
					a.setDaemon(true);
					a.start();
				}
				
				while(endThreads.size()!=numThreads) {
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

				Random r = new Random();
				Solution tempSolution = currentSolution;
				if(st==6) { 
					Integer[] tS = successThreads.toArray(new Integer[successThreads.size()]);
					tempSolution = threads[tS[r.nextInt(tS.length)]].sol;
				}
				else {
					 tempSolution = threads[r.nextInt(numThreads)].sol;
				}
				double was = tempSolution.getCost();
				double is = currentSolution.getCost();
				//System.out.println("COST:"+was);
				if(Math.abs(tempSolution.getCost()-currentSolution.getCost())<1e-10)
					++numberOfAcceptance;
				else
					numberOfAcceptance = 0;
				currentSolution = tempSolution;
				System.out.println(System.currentTimeMillis()-now);
			}
			
			
			System.out.println(String.format("T is %1$f. Cost is %2$f. End is %3$d", 
		    					shedule.getTemperature(), currentSolution.getCost(), numberOfAcceptance));
			//System.out.println(currentSolution.toString());
			shedule.anneal();
			++NNN;
		}
		System.out.println(currentSolution.toString());
		System.out.println(currentSolution.getCost());
		System.out.println(NNN);
		
	}
	
	public void CUDANext() {
		
	}
	public boolean isEnd() {
		final int end=20;
		return numberOfAcceptance>=end;
	}
	
	public Solution getSolution() {
		return currentSolution;
	}
	
	public double getTemperature() {
		return shedule.getTemperature();
	}
	
	
}

class defaultCoolingShedule 
	implements CoolingShedule
	{
		private double T;
		private Solution S;
		public defaultCoolingShedule(Solution S) {
			this.S = S;
			T = getInitialTemperature();
		}
		public double getInitialTemperature() {
			double percentToAccept = 0.3;
			double percentToWorse = 0.15;
			return (percentToWorse/(-Math.log(percentToAccept)))*S.getCost();
		}

		public void anneal() {
			T = T*0.98;
		}

		public double getTemperature() {
			return T;
		}
	}

